#include <string>
#include <iostream>
#include <sstream>
#include <vector>

#include "baseCard.h"
#include "baseBoard.h"
#include "baseToken.h"
#include "baseEffect.h"

class baseCard;
class baseToken;

using namespace std;

baseBoard::baseBoard(int aSize) : width(aSize), height(aSize)
{
    initCardsMatrix(aSize, aSize);
    initCostModificationMatrix(aSize, aSize);
}

baseBoard::baseBoard(int aWidth, int aHeight) : width(aWidth), height(aHeight)
{
    initCardsMatrix(aWidth, aHeight);
    initCostModificationMatrix(aWidth, aHeight);
}

int baseBoard::getHeight()
{
    return this->height;
}

int baseBoard::getWidth()
{
    return this->width;
}

vector<vector<baseCard*>> baseBoard::getCardsMatrix()
{
    return this->cardsMatrix;
}

string baseBoard::toString()
{
    stringstream out;

    out << "Board: " << "width " << getWidth() << " " << "height " << getHeight() << endl;

    out << cardsMatrixToString();

    return out.str();
}

void baseBoard::toString(ostream &out)
{
    out << toString();
}

void baseBoard::toString(string &str)
{
    str = str + toString();
}

string baseBoard::cardsMatrixToString()
{
    stringstream out;

    for(int i = 0; i<getWidth(); i++)
    {
        for(int j = 0; j<getHeight(); j++)
        {
            if(cardsMatrix[i][j])
                out << cardsMatrix[i][j]->toString() << endl;
            else
                out << "Empty space" << endl;
        }
    }

    return out.str();
}

void baseBoard::addCard(baseCard& aCard)
{
    for(int j=0; j<getHeight(); j++)
    {
        for(int i=0; i<getWidth(); i++)
        {
            if(!cardsMatrix[i][j])
            {
                addCard(aCard, i, j);
                return;
            }
        }
    }
}

void baseBoard::addCard(baseCard& aCard, int column, int row)
{
    if((column < getWidth() && row < getHeight())
       && !cardsMatrix[column][row])
    {
        cardsMatrix[column][row] = &aCard;
    }
}

void baseBoard::removeCard(baseCard& aCard)
{
    for(int j=0; j<getHeight(); j++)
    {
        for(int i=0; i<getWidth(); i++)
        {
            if(cardsMatrix[i][j]
               && *cardsMatrix[i][j] == aCard)
            {
                cardsMatrix[i][j] = NULL;
                return;
            }
        }
    }
}

baseCard* baseBoard::getCard(int column, int row)
{
    return cardsMatrix[column][row];
}

void baseBoard::moveUpColumn(int aColumnNum)
{
    if(aColumnNum < getWidth())
    {
        baseCard* temp;

        for(int i=0; i<getHeight(); i++)
        {
            if(i == 0)
                temp = cardsMatrix[aColumnNum][i];

            if(i+1 != getHeight())
                cardsMatrix[aColumnNum][i] = cardsMatrix[aColumnNum][i+1];
            else
                cardsMatrix[aColumnNum][i] = temp;
        }
    }
}

void baseBoard::moveDownColumn(int aColumnNum)
{
    if(aColumnNum < getWidth())
    {
        baseCard* temp;

        for(int i=getHeight()-1; i>=0; i--)
        {
            if(i == getHeight()-1)
                temp = cardsMatrix[aColumnNum][i];

            if(i != 0)
                cardsMatrix[aColumnNum][i] = cardsMatrix[aColumnNum][i-1];
            else
                cardsMatrix[aColumnNum][i] = temp;
        }
    }
}

void baseBoard::moveRightRow(int aRowNum)
{
    if(aRowNum < getHeight())
    {
        baseCard* temp;

        for(int i=getWidth()-1; i>=0; i--)
        {
            if(i == getWidth()-1)
                temp = cardsMatrix[i][aRowNum];

            if(i != 0)
                cardsMatrix[i][aRowNum] = cardsMatrix[i-1][aRowNum];
            else
                cardsMatrix[i][aRowNum] = temp;
        }
    }
}

void baseBoard::moveLeftRow(int aRowNum)
{
    if(aRowNum < getHeight())
    {
        baseCard* temp;

        for(int i=0; i<getWidth(); i++)
        {
            if(i == 0)
                temp = cardsMatrix[i][aRowNum];

            if(i+1 != getWidth())
                cardsMatrix[i][aRowNum] = cardsMatrix[i+1][aRowNum];
            else
                cardsMatrix[i][aRowNum] = temp;
        }
    }
}

int baseBoard::getCostModifier(baseCard& aCard)
{
    int res = 0;

    for(int j=0; j<getHeight(); j++)
    {
        for(int i=0; i<getWidth(); i++)
        {
            if(cardsMatrix[i][j]
               && *cardsMatrix[i][j] == aCard)
            {
                res = costModificationMatrix[i][j];
                return res;
            }
        }
    }

    return res;
}

void baseBoard::display()
{
    char *buffer;
    int cardHeight = 20;
    int cardWidth = 20;
    int interLine = cardWidth*getWidth()+1;
    int lineSize = interLine*cardHeight;
    int allocatedSize = lineSize*getHeight()+interLine*(getHeight()+1)+1;

    buffer = (char*)malloc(sizeof(char)*allocatedSize);
    memset(buffer, ' ', allocatedSize);
    buffer[allocatedSize-1] = '\0';

    printGrid(buffer, allocatedSize, cardWidth, cardHeight);

    for(int i = 0; i<getWidth(); i++)
    {
        for(int j = 0; j<getHeight(); j++)
        {
            int offset = cardWidth*(i)+(lineSize+interLine)*j;
            char temp[1024];

            if(!cardsMatrix[i][j])
            {
                offset += interLine; //line 1 = empty
                offset += interLine; //line 2 = empty
                offset += interLine; //line 3 = empty
                offset += interLine; //line 4 = empty
                offset += interLine; //line 5 = empty
                offset += interLine; //line 6 = empty
                offset += interLine; //line 7 = empty
                offset += interLine; //line 8 = empty
                offset += interLine; //line 9 = empty
                offset += interLine; //line 10 = empty
                offset += interLine; //line 11 = empty
                offset += interLine; //line 12 = empty
                offset += interLine; //line 13 = empty
                offset += interLine; //line 14 = empty
                offset += interLine; //line 15 = empty
                offset += interLine; //line 16 = empty
                offset += interLine; //line 17 = empty
                offset += interLine; //line 18 = empty

                offset += interLine; //line 19 = Board value
                memcpy(buffer+offset+1, "Delta: ", strlen("Delta: "));
                _itoa_s(this->costModificationMatrix[i][j], temp, 1024, 10);
                memcpy(buffer+offset+1+strlen("Delta: "), temp, strlen(temp));

                offset += interLine; //line 20 = empty
            }
            else
            {
                offset += interLine; //line 1 = empty
                offset += interLine; //line 2 = empty

                offset += interLine; //line 3 = card name
                memcpy(temp, cardsMatrix[i][j]->getName().c_str(), cardsMatrix[i][j]->getName().length());
                memcpy(buffer+offset+1, temp, cardsMatrix[i][j]->getName().length());

                offset += interLine; //line 4 = empty
                offset += interLine; //line 5 = empty

                offset += interLine; //line 6 = card cost
                memcpy(buffer+offset+1, "Cost: ", strlen("Cost: "));
                _itoa_s(cardsMatrix[i][j]->getCost(), temp, 1024, 10);
                memcpy(buffer+offset+1+strlen("Cost: "), temp, strlen(temp));

                offset += interLine; //line 7 = empty

                offset += interLine; //line 8 = card point
                memcpy(buffer+offset+1, "Points: ", strlen("Points: "));
                _itoa_s(cardsMatrix[i][j]->getPoint(), temp, 1024, 10);
                memcpy(buffer+offset+1+strlen("Points: "), temp, strlen(temp));

                offset += interLine; //line 9 = empty

                offset += interLine; //line 10 = "Effect: "
                if(cardsMatrix[i][j]->getEffect())
                {
                    memcpy(buffer+offset+1, "Effect: ", strlen("Effect: "));
                    offset += interLine; //line 11 = the effect
                    memcpy(temp, cardsMatrix[i][j]->getEffect()->getDescription().c_str(), cardsMatrix[i][j]->getEffect()->getDescription().length());
                    memcpy(buffer+offset+1, temp, cardsMatrix[i][j]->getEffect()->getDescription().length());
                }
                else
                {
                    offset += interLine; //line 11 = empty
                }

                offset += interLine; //line 12 = empty
                offset += interLine; //line 13 = empty

                offset += interLine; //line 14 = Tokens
                vector<string> tokenColors;
                cardsMatrix[i][j]->getTokenColors(tokenColors);
                for(int w=0; w<(int)tokenColors.size(); w++)
                {
                    memcpy(temp, tokenColors[w].c_str(), tokenColors[w].length());
                    memcpy(buffer+offset+1, temp, tokenColors[w].length());
                    _itoa_s(cardsMatrix[i][j]->countTokens(tokenColors[w]), temp, 1024, 10);
                    memcpy(buffer+offset+1+tokenColors[w].length()+1, temp, strlen(temp));
                    offset += interLine; //line 15 = empty
                }

                // 4 colors max
                for(int x=0; x<(int)(4-tokenColors.size()); x++)
                {
                    offset += interLine; //line 15 16 17 18 = empty
                }

                offset += interLine; //line 19 = Board value
                memcpy(buffer+offset+1, "Delta: ", strlen("Delta: "));
                _itoa_s(this->costModificationMatrix[i][j], temp, 1024, 10);
                memcpy(buffer+offset+1+strlen("Delta: "), temp, strlen(temp));

                offset += interLine; //line 20 = empty
            }
        }
    }
    cout << buffer << endl;
}

void baseBoard::printGrid(char* buffer, int allocatedSize, int cardWidth, int cardHeight)
{
    int interLine = cardWidth*getWidth()+1;
    int lineSize = interLine*cardHeight;

    for(int k=interLine; k<allocatedSize; k+=interLine)
    {
        buffer[k-1] = '\n'; 
    }

    for(int k=0; k<allocatedSize; k+=lineSize+interLine)
    {
        for(int l=0; l<cardWidth*getWidth(); l++)
            buffer[k+l] = '-'; 
    }

    for(int k=0; k<allocatedSize-1; k+=interLine)
    {
        if(k%(lineSize+interLine) != 0)
        {
            for(int l=0; l<cardWidth*getWidth(); l+=cardWidth)
            {
                buffer[k+l] = '|';
                buffer[k+l+cardWidth-1] = '|';
            }
        }
    }
}

void baseBoard::initCardsMatrix(int aWidth, int aHeight)
{
    cardsMatrix.resize(aWidth);
    for(int i=0; i<aWidth; i++)
    {
        cardsMatrix[i].resize(aHeight);
    }
}

void baseBoard::initCostModificationMatrix(int aWidth, int aHeight)
{
    costModificationMatrix.resize(aWidth);
    for(int i=0; i<aWidth; i++)
    {
        costModificationMatrix[i].resize(aHeight);
    }

    for(int j=0; j<aWidth; j++)
    {
        for(int k=0; k<aHeight; k++)
        {
            if( j == aWidth/2 && k == aHeight/2 )
                costModificationMatrix[j][k] = -2;
            else
            {
                if( (j+k)%2 == 0 )
                    costModificationMatrix[j][k] = -1;
                else if( (j+k)%2 != 0 )
                    costModificationMatrix[j][k] = 1;
            }
        }
    }
}

int baseBoard::countCards()
{
    int res = 0;

    for(int j=0; j<getHeight(); j++)
    {
        for(int i=0; i<getWidth(); i++)
        {
            if(cardsMatrix[i][j])
                res++;
        }
    }

    return res;
}

bool baseBoard::isFull()
{
    return (countCards() >= (width*height));
}
